using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
using System.Text;
using PerfAssist.Common.Editor;
using PerfAssist.Common.usmooth.Common;
using UnityEditor;
using UnityEditor.MemoryProfiler;
using UnityEditorInternal;
using UnityEngine;

namespace PerfAssist.MemoryProfilerAdvanced.Editor.PAContrib.TrackerModes
{
    public static class TrackerModeConsts
    {
        public static string[] Modes =
        {
            "Editor",
            "Remote",
#if JX3M
        "RemoteEx",
#endif
            "File"
        };

        public static string[] ModesDesc =
        {
            "'Editor': connects to the local in-editor game (Native types only).",
            "'Remote': connects to the remote device (C# types if il2cpp is enabled).",
#if JX3M
        "'RemoteEx': (experimental) connects to the remote device using customized connection (should be more stable).",
#endif
            "'File': opens a saved session from local file system."
        };

        public static readonly string RemoteTag = "-Remote-";
        public static readonly string EditorTag = "-Editor";

        public static readonly string SnapshotBinPostfix = ".memsnap";
        public static readonly string SnapshotJsonPostfix = ".json";
    }

    public static class TrackerModeUtil
    {
        public static bool Handle_ServerLogging(eNetCmd cmd, UsCmd c)
        {
            var pkt = new UsLogPacket(c);

            var logTypeStr = "";
            switch (pkt.LogType)
            {
                case UsLogType.Error:
                case UsLogType.Exception:
                case UsLogType.Assert:
                case UsLogType.Warning:
                    logTypeStr = string.Format("{1}", pkt.LogType);
                    break;

                case UsLogType.Log:
                default:
                    break;
            }

            var timeStr = string.Format("{0:0.00}({1})", pkt.RealtimeSinceStartup, pkt.SeqID);

            var ret = string.Format("{0} {1} <color=white>{2}</color>", timeStr, logTypeStr, pkt.Content);

            if (!string.IsNullOrEmpty(pkt.Callstack)) ret += string.Format("\n<color=gray>{0}</color>", pkt.Callstack);

            Debug.Log(ret);

            return true;
        }

        public static bool SaveSnapshotFiles(string targetSession, string targetName,
            PackedMemorySnapshot packed /*, CrawledMemorySnapshot unpacked*/)
        {
            var targetDir = Path.Combine(MemUtil.SnapshotsDir, targetSession);
            if (!Directory.Exists(targetDir))
                Directory.CreateDirectory(targetDir);

            if (!SaveSnapshotBin(targetDir, targetName + TrackerModeConsts.SnapshotBinPostfix, packed))
                return false;
            //if (!TrackerModeUtil.SaveSnapshotJson(targetDir, targetName + TrackerModeConsts.SnapshotJsonPostfix, unpacked))
            //    return false;

            Debug.LogFormat("Snapshot saved successfully. (dir: {0}, name: {1})", targetDir, targetName);
            return true;
        }

        public static bool SaveSnapshotBin(string binFilePath, string binFileName, PackedMemorySnapshot packed)
        {
            try
            {
                var fullName = Path.Combine(binFilePath, binFileName);
                if (!File.Exists(fullName))
                {
                    var bf = new BinaryFormatter();
                    using (Stream stream = File.Open(fullName, FileMode.Create))
                    {
                        bf.Serialize(stream, packed);
                    }
                }

                return true;
            }
            catch (Exception ex)
            {
                Debug.LogError(string.Format("save snapshot error ! msg ={0}", ex.Message));
                Debug.LogException(ex);
                return false;
            }
        }

        public static bool SaveSnapshotJson(string jsonFilePath, string jsonFileName, CrawledMemorySnapshot unpacked)
        {
            try
            {
                var jsonContent = ResolvePackedForJson(unpacked);
                if (string.IsNullOrEmpty(jsonContent))
                    throw new Exception("resolve failed.");

                if (!Directory.Exists(jsonFilePath))
                    Directory.CreateDirectory(jsonFilePath);

                var jsonFile = Path.Combine(jsonFilePath, jsonFileName);
                var fileInfo = new FileInfo(jsonFile);
                var sw = fileInfo.CreateText();
                sw.Write(jsonContent);
                sw.Close();
                sw.Dispose();
            }
            catch (Exception ex)
            {
                Debug.LogError(string.Format("save text error ! msg ={0}", ex.Message));
                Debug.LogException(ex);
                return false;
            }

            return true;
        }

        public static void Connect(string ip)
        {
            //if (NetManager.Instance == null)
            //    return;

            ProfilerDriver.connectedProfiler = -1;
            //if (NetManager.Instance.IsConnected)
            //    NetManager.Instance.Disconnect();

            try
            {
                if (!MemUtil.ValidateIPString(ip))
                    throw new Exception("Invaild IP");

                //if (!NetManager.Instance.Connect(ip))
                //    throw new Exception("Bad Connect");

                if (!MemUtil.IsLocalhostIP(ip))
                {
                    ProfilerDriver.DirectIPConnect(ip);
                    if (!MemUtil.IsProfilerConnectedRemotely)
                        throw new Exception("Bad Connect");
                }

                EditorPrefs.SetString(MemPrefs.LastConnectedIP, ip);
            }
            catch (Exception ex)
            {
                EditorWindow.focusedWindow.ShowNotification(new GUIContent(string.Format("Connecting '{0}' failed: {1}", ip,
                    ex.Message)));
                Debug.LogException(ex);

                ProfilerDriver.connectedProfiler = -1;
                //if (NetManager.Instance.IsConnected)
                //    NetManager.Instance.Disconnect();
            }
        }

        public static string ResolvePackedForJson(CrawledMemorySnapshot packed)
        {
            if (packed == null)
                return null;
            var _unpacked = packed;
            var types = new Dictionary<string, MemType>();

            foreach (var thingInMemory in packed.allObjects)
            {
                var typeName = MemUtil.GetGroupName(thingInMemory);
                if (typeName.Length == 0)
                    continue;
                var category = MemUtil.GetCategory(thingInMemory);
                var item = new MemObject(thingInMemory, _unpacked);
                MemType theType;
                if (!types.ContainsKey(typeName))
                {
                    theType = new MemType();
                    theType.TypeName = MemUtil.GetCategoryLiteral(thingInMemory) + typeName;
                    theType.Category = category;
                    theType.Objects = new List<object>();
                    types.Add(typeName, theType);
                }
                else
                {
                    theType = types[typeName];
                }

                theType.AddObject(item);
            }

            //协议格式:
            //Data:
            //"obj" = "TypeName,Category,Count,size"
            //"info" ="RefCount,size,InstanceName(hashCode),address,typeDescriptionIndex(hashCode)"
            //typeDescription:
            //InstanceNames:

            var typeDescDict = new Dictionary<int, string>();
            var instanceNameDict = new Dictionary<int, string>();
            var jsonData = new JsonData();
            foreach (var type in types)
            {
                var typeData = new JsonData();
                typeData["Obj"] = type.Key + "," + type.Value.Category + "," + type.Value.Count + "," + type.Value.Size;

                var objectDatas = new JsonData();
                foreach (var obj in type.Value.Objects)
                {
                    var objectData = new JsonData();
                    var memObj = obj as MemObject;
                    string dataInfo;
                    var instanceNameHash = memObj.InstanceName.GetHashCode();
                    if (!instanceNameDict.ContainsKey(instanceNameHash))
                        instanceNameDict.Add(instanceNameHash, memObj.InstanceName);

                    dataInfo = memObj.RefCount + "," + memObj.Size + "," + instanceNameHash;
                    if (type.Value.Category == 2)
                    {
                        var manged = memObj._thing as ManagedObject;
                        var typeDescriptionHash = manged.typeDescription.name.GetHashCode();
                        if (!typeDescDict.ContainsKey(typeDescriptionHash))
                            typeDescDict.Add(typeDescriptionHash, manged.typeDescription.name);
                        dataInfo += "," + Convert.ToString((int) manged.address, 16) + "," + typeDescriptionHash;
                    }

                    objectData["info"] = dataInfo;
                    objectDatas.Add(objectData);
                }

                typeData["memObj"] = objectDatas;
                jsonData.Add(typeData);
            }

            var resultJson = new JsonData();
            resultJson["Data"] = jsonData;

            var sb = new StringBuilder();
            foreach (var key in typeDescDict.Keys) sb.Append("[[" + key + "]:" + typeDescDict[key] + "],");
            resultJson["TypeDescs"] = sb.ToString();
            sb.Remove(0, sb.Length);

            foreach (var key in instanceNameDict.Keys) sb.Append("[[" + key + "]:" + instanceNameDict[key] + "],");
            resultJson["InstanceNames"] = sb.ToString();
            return resultJson.ToJson();
        }
    }
}